在上一篇文章中,我們實作了一個簡單的用戶資料管理系統,並展示了如何結合 Rust 與 React 進行全端開發。然而,對於一個完整的應用來說,通常需要有登入頁面,讓使用者先通過身份驗證,然後進入後台管理頁面進行操作。今天,我們將擴展上一個範例,將管理頁面從 App.js
中獨立出來,並且新增一個簡單的登入頁,示範如何使用 <Route>
進行頁面切換。
在進行頁面管理之前,我們需要安裝 react-router-dom
,這個套件用於實現 React 應用中的路由管理。打開你的終端機,進入 frontend
前端資料夾,執行以下指令來安裝套件:
npm install react-router-dom
安裝完成後,我們就可以在專案中使用 <Route>
來進行頁面的切換了。
首先,我們將原本的 App.js
中的用戶資料管理部分抽取出來,並搬到一個新的元件中。我們會將它稱為 UserManagement.js
,這將會是用來顯示在後台管理系統中的頁面。
在我們修改之前,原本的專案結構是這樣的:
myproject/
├── frontend/
│ ├── build/
│ ├── node_modules/
│ ├── public/
│ ├── src/
│ │ ├── App.css
│ │ └── App.js
│ ├── .gitignore
│ ├── package-lock.json
│ ├── package.json
│ └── README.md
└── rust-restful-api/
├── src/
└── target/
└──.gitignore
└──Cargo.lock
└──Cargo.toml
其中,App.js
文件中包含了用戶資料管理的相關邏輯與頁面結構。為了更好地組織代碼,我們希望將這些內容從 App.js
中抽取出來,並將用戶資料管理部分移至一個新的元件 UserManagement.js
,但是記得要修改原本的 function App()...
跟 export default App;
當中的 App
,改成 UserManagement
,然後App.css
也要改成 UserManagement.css
,實際完整修改程式碼如下:
import { useState, useEffect } from 'react'; // 從 React 中導入 useState 和 useEffect,用來管理狀態與副作用
import axios from 'axios'; // 從 axios 套件導入,用來處理 HTTP 請求
import './UserManagement.css'; // 導入外部的 CSS 檔案,設置樣式
function UserManagement() {
const [users, setUsers] = useState([]); // 定義一個狀態變數 users,用來儲存用戶列表,初始值為空陣列
const [newUserName, setNewUserName] = useState(''); // 定義一個狀態變數 newUserName,用來儲存新增用戶的名字,初始值為空字串
const [updateUserName, setUpdateUserName] = useState(''); // 定義一個狀態變數 updateUserName,用來儲存更新用戶的名字,初始值為空字串
const [userIdToUpdate, setUserIdToUpdate] = useState(''); // 定義一個狀態變數 userIdToUpdate,用來儲存需要更新的用戶 ID,初始值為空字串
// 在組件首次渲染後,執行獲取用戶列表的函數
useEffect(() => {
fetchUsers(); // 執行獲取用戶的函數
}, []); // 這裡的空陣列表示只在組件首次載入時執行一次
// 獲取用戶列表的非同步函數
const fetchUsers = async () => {
try {
const response = await axios.get('/users'); // 向後端發送 GET 請求,獲取用戶資料
setUsers(response.data); // 將獲取到的用戶資料設定到 users 狀態中
} catch (error) {
console.error('Error fetching users:', error); // 如果發生錯誤,顯示錯誤訊息
}
};
// 新增用戶的非同步函數
const createUser = async () => {
try {
if (!newUserName) return; // 如果沒有輸入用戶名稱,則不執行新增操作
await axios.post('/users', { name: newUserName }); // 向後端發送 POST 請求,傳送新用戶資料
setNewUserName(''); // 清空輸入框的內容
fetchUsers(); // 重新獲取用戶列表,更新畫面
} catch (error) {
console.error('Error creating user:', error); // 如果發生錯誤,顯示錯誤訊息
}
};
// 刪除用戶的非同步函數
const deleteUser = async (id) => {
try {
await axios.delete(`/users/${id}`); // 向後端發送 DELETE 請求,刪除指定 ID 的用戶
fetchUsers(); // 重新獲取用戶列表,更新畫面
} catch (error) {
console.error('Error deleting user:', error); // 如果發生錯誤,顯示錯誤訊息
}
};
// 更新用戶的非同步函數
const updateUser = async () => {
try {
if (!userIdToUpdate || !updateUserName) return; // 如果沒有輸入要更新的用戶 ID 或新名稱,則不執行更新操作
await axios.put(`/users/${userIdToUpdate}`, { name: updateUserName }); // 向後端發送 PUT 請求,更新指定 ID 的用戶資料
setUserIdToUpdate(''); // 清空輸入框的內容
setUpdateUserName(''); // 清空輸入框的內容
fetchUsers(); // 重新獲取用戶列表,更新畫面
} catch (error) {
console.error('Error updating user:', error); // 如果發生錯誤,顯示錯誤訊息
}
};
return (
<div className="App"> {/* 最外層的 div,套用 CSS 樣式 App */}
<header className="App-header"> {/* 頁面的 header,套用 CSS 樣式 App-header */}
<h1>會員資料管理系統</h1> {/* 頁面標題 */}
<div className="form-section"> {/* 用於表單的區塊,包含新增和更新會員 */}
<div className="form-group"> {/* 表單組塊,用於新增會員 */}
<h2>新增會員</h2> {/* 小標題:新增會員 */}
<input
type="text"
value={newUserName} // 綁定新用戶名稱的狀態變數
onChange={(e) => setNewUserName(e.target.value)} // 當輸入變更時,更新 newUserName 狀態
placeholder="輸入會員名稱" // 提示使用者輸入會員名稱
/>
<button onClick={createUser}>新增會員</button> {/* 點擊按鈕時執行 createUser 函數 */}
</div>
<div className="form-group"> {/* 表單組塊,用於更新會員 */}
<h2>更新會員</h2> {/* 小標題:更新會員 */}
<input
type="text"
value={userIdToUpdate} // 綁定需要更新的會員 ID 的狀態變數
onChange={(e) => setUserIdToUpdate(e.target.value)} // 當輸入變更時,更新 userIdToUpdate 狀態
placeholder="輸入會員ID" // 提示使用者輸入會員 ID
/>
<input
type="text"
value={updateUserName} // 綁定新的會員名稱的狀態變數
onChange={(e) => setUpdateUserName(e.target.value)} // 當輸入變更時,更新 updateUserName 狀態
placeholder="輸入新的會員名稱" // 提示使用者輸入新的會員名稱
/>
<button onClick={updateUser}>更新會員</button> {/* 點擊按鈕時執行 updateUser 函數 */}
</div>
</div>
<div className="table-section"> {/* 表格區塊,用於顯示會員列表 */}
<h2>會員列表</h2> {/* 小標題:會員列表 */}
<table className="user-table"> {/* 使用者表格,套用 CSS 樣式 user-table */}
<thead>
<tr>
<th>會員ID</th> {/* 表格標題:會員 ID */}
<th>會員名稱</th> {/* 表格標題:會員名稱 */}
<th>操作</th> {/* 表格標題:操作 */}
</tr>
</thead>
<tbody>
{users.map((user) => ( // 遍歷 users 陣列,顯示每個用戶的資料
<tr key={user.id}> {/* 每一列的唯一 key 為 user.id */}
<td>{user.id}</td> {/* 顯示用戶的 ID */}
<td>{user.name}</td> {/* 顯示用戶的名稱 */}
<td>
<button className="edit-btn" onClick={() => deleteUser(user.id)}>刪除</button> {/* 刪除按鈕,點擊後執行 deleteUser 函數 */}
</td>
</tr>
))}
</tbody>
</table>
</div>
</header>
</div>
);
}
export default UserManagement; // 導出 UserManagement 組件,以便在其他地方使用
記得 App.css
也要改成 UserManagement.css
放在 components
資料夾,然後我們還會需要建立一個新的分頁,以 Login.js
與 Login.css
命名,搬運後的專案結構會變成這樣:
myproject/
├── frontend/
│ ├── build/
│ ├── node_modules/
│ ├── public/
│ ├── src/
│ │ ├── components/
│ │ │ ├── UserManagement.js # 用戶資料管理頁面
│ │ │ ├── UserManagement.css # 用戶資料管理頁面樣式
│ │ │ ├── Login.js # 登入頁面
│ │ │ ├── Login.css # 登入頁面樣式
│ │ ├── App.js # 路由邏輯,移除了管理頁邏輯
│ │ └── App.css
│ └── .gitignore
│ └── package-lock.json
│ └── package.json
│ └── README.md
└── rust-restful-api/
├── src/
└── target/
└── .gitignore
└── Cargo.lock
└── Cargo.toml
我們新增了一個 components
資料夾來存放拆分出來的元件,其中 UserManagement.js
是用來顯示在後台管理系統中的頁面,Login.js
則是新的登入頁面。
這樣的調整讓專案結構更加清晰,並且使得每個元件的責任分離,便於管理和維護。後續我們會在 App.js
中使用路由來管理不同頁面的顯示,現在我們要先完成登入頁的內容設定。
接下來,我們將建立一個簡單的登入頁面,讓使用者輸入帳號與密碼進行登入,登入成功後跳轉到管理頁面。
src/components/Login.js
:
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import './Login.css';
function Login() {
const [username, setUsername] = useState(''); // 帳號
const [password, setPassword] = useState(''); // 密碼
const navigate = useNavigate(); // 用於頁面導航
const handleLogin = (e) => {
e.preventDefault();
// 簡單的驗證邏輯(實際上應該連接後端進行認證)
if (username === 'admin' && password === 'password') {
navigate('/management'); // 登入成功,跳轉到管理頁面
} else {
alert('帳號或密碼錯誤');
}
};
return (
<div className="login">
<h2>登入頁面</h2>
<form onSubmit={handleLogin}>
<div className="form-group">
<label htmlFor="username">帳號</label>
<input
type="text"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div className="form-group">
<label htmlFor="password">密碼</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit">登入</button>
</form>
</div>
);
}
export default Login;
這個登入頁面會簡單檢查帳號是否為 admin
、密碼是否為 password
,如果驗證成功,使用者會被導向用戶管理頁面。當然,實際應用中應該與後端進行身份驗證。
我們也可以設置一些簡單的樣式,讓頁面看起來更美觀。這是 Login.css
的範例:
/* 確保 body 和 html 的高度佔滿整個視窗 */
html,
body {
height: 100%;
margin: 0;
background-color: #f9f3e7;
/* 背景色和登入頁背景一致 */
}
/* 父元素的 display 設定為 flex,以實現垂直和水平居中 */
.login {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
height: 100vh;
/* 確保區塊佔滿整個視窗高度 */
padding: 20px;
background-color: #f9f3e7;
border-radius: 10px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
width: 100%;
margin-left: auto;
margin-right: auto;
}
.form-group {
margin: 20px 0;
text-align: left;
}
label {
display: block;
margin-bottom: 8px;
font-size: 14px;
color: #5f5f5f;
}
input {
padding: 12px;
width: 100%;
margin: 8px 0;
border: 1px solid #ccc;
border-radius: 5px;
box-sizing: border-box;
background-color: #fcfbf9;
font-size: 16px;
}
button {
padding: 12px 24px;
background-color: #e07a5f;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
width: 100%;
font-size: 16px;
margin-top: 20px;
transition: background-color 0.3s ease;
}
button:hover {
background-color: #c5533d;
}
Login.css
一樣放在 components
資料夾內讓 Login.js
引用就可以囉
現在,我們來修改 App.js
,加入登入頁面和管理頁面的路由邏輯:
src/App.js
:
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; // 導入路由相關模組
import Login from './components/Login'; // 登入頁
import UserManagement from './components/UserManagement'; // 用戶管理頁
import './App.css';
function App() {
return (
<Router>
<div className="App">
{/* 設置路由 */}
<Routes>
<Route path="/" element={<Login />} /> {/* 預設路徑為登入頁 */}
<Route path="/management" element={<UserManagement />} /> {/* 管理頁 */}
</Routes>
</div>
</Router>
);
}
export default App;
在這裡,我們設置了兩個主要的路由:
/
:對應登入頁面。/management
:對應用戶管理頁面,使用者登入成功後會被導向此頁。現在,整個應用架構已經完成。我們只需要再次執行 npm run build
來打包 React 前端應用,並啟動 Rust 後端伺服器即可測試登入與分頁功能。
# frontend 路徑下執行,以重新打包
npm run build
# rust-restful-api 路徑下執行,開啟網頁應用
cargo run
當你進入 http://127.0.0.1:8000
,你將首先看到登入頁面,輸入正確的帳號和密碼後,會跳轉到會員管理的後台頁面。
登入頁面
輸入帳號為 admin
密碼為 password
之後,就會進入到上一篇的會員資料管理頁
這就是透過 <Route>
元件進行 React 網頁開發應用的分頁管理工具的範例,接下來我們在詳細說明一下上面的程式碼的細節。
在這個段落中,我們將逐步解析關鍵的程式碼部分,並結合 React 元件化設計 和 React Hooks 的概念,幫助讀者深入理解每個部分的運作原理與背後的邏輯。
React 的元件化設計是一個非常核心的思想,它將 UI 切割成許多可重複使用的元件。每個元件負責一小部分的功能或視覺展示,並能獨立運作。這樣的結構讓代碼變得更加模組化和清晰,元件可以很容易被重複使用或單獨進行測試。
在我們的專案中,Login.js
和 UserManagement.js
都是功能性元件 (Functional Components),它們通過接收輸入 (props) 來決定輸出的渲染結果。在 React 16.8 版本之後,React 引入了 Hooks,使得在功能性元件中使用狀態與其他 React 特性成為可能,而無需使用類別元件。
Hooks 是 React 的一個關鍵功能,允許你在不需要類別元件的情況下使用狀態、生命週期等特性。這裡我們用到了兩個最常見的 Hooks——useState
和 useEffect
。
useState
:用於在功能性元件中添加狀態。每次狀態改變時,React 會重新渲染元件以反映新的狀態。
例如,在 UserManagement
和 Login
中,我們使用 useState
來管理輸入框的數值和用戶列表:
const [users, setUsers] = useState([]); // 管理用戶列表
const [newUserName, setNewUserName] = useState(''); // 管理新用戶名
useEffect
:用於執行副作用操作,例如數據請求、訂閱或手動操作 DOM。useEffect
是一個具有副作用的 Hook,它會在組件渲染完成後運行。
在 UserManagement
中,我們用 useEffect
來在組件掛載後立即發起數據請求:
useEffect(() => {
fetchUsers(); // 獲取用戶數據
}, []); // 空陣列代表只在第一次渲染時執行一次
useEffect
會根據它的依賴項(這裡是空陣列 []
)來決定何時執行。這樣的機制讓我們能夠控制元件的生命週期行為,並在需要的時候執行特定操作。
react-router-dom
的應用react-router-dom
是 React 的路由管理工具,它允許我們在單頁應用中創建多個不同的頁面,並在這些頁面之間進行無縫切換。這讓應用變得更加動態和互動性更強。在這裡,我們利用以下核心組件來實現頁面之間的切換:
<Router>
:它是路由管理的容器,所有路由設定都應包含在其中。<Routes>
和 <Route>
:這些用來定義不同路徑與對應的元件顯示邏輯。在 App.js
中,我們定義了兩個路由路徑:
import Login from './components/Login'; // 登入頁
import UserManagement from './components/UserManagement'; // 用戶管理頁
...
<Routes>
<Route path="/" element={<Login />} /> {/* 預設路徑為登入頁 */}
<Route path="/management" element={<UserManagement />} /> {/* 管理頁 */}
</Routes>
"/"
對應 Login
登入頁面,"/management"
對應 UserManagement
用戶管理頁面。當使用者輸入正確的帳號密碼時,頁面會跳轉至管理頁。其中 Login
跟 UserManagement
都是在該程式碼內已經被編輯好的 元件
,並且透過 export default Login;
與 export default UserManagement;
的方式宣告可被引用,因此在 App.js
當中就可以透過 import
方式加入到 <Route>
內當作完整分頁。
登入頁面 Login.js
的邏輯非常簡單,使用了 useState
來處理帳號和密碼的輸入,並利用 useNavigate
進行頁面的跳轉。
const [username, setUsername] = useState(''); // 帳號
const [password, setPassword] = useState(''); // 密碼
const navigate = useNavigate(); // 用於頁面導航
當使用者點擊登入按鈕時,會觸發 handleLogin
函數。此函數會檢查輸入的帳號和密碼是否正確,並根據結果決定是否跳轉到用戶管理頁面:
if (username === 'admin' && password === 'password') {
navigate('/management'); // 登入成功,跳轉到管理頁面
} else {
alert('帳號或密碼錯誤');
}
這裡的頁面跳轉是透過 useNavigate
來實現的,它是 React 路由提供的工具,用於程式性地進行頁面跳轉。
用戶管理頁面 UserManagement.js
使用了 axios
來進行 HTTP 請求,並使用 useState
來管理用戶資料。
當頁面加載時,useEffect
會自動調用 fetchUsers
函數,這個函數向後端發送一個 GET 請求來獲取所有的用戶資料,並將它們存儲在 users
狀態變數中。
useEffect(() => {
fetchUsers();
}, []); // 空依賴陣列表示只在頁面加載時執行一次
const fetchUsers = async () => {
try {
const response = await axios.get('/users');
setUsers(response.data); // 將獲取到的用戶資料存入狀態
} catch (error) {
console.error('Error fetching users:', error);
}
};
當使用者輸入新用戶名並點擊新增按鈕時,createUser
函數會被觸發。這個函數會向後端發送一個 POST 請求,將新的用戶資料提交到伺服器,並在成功後更新用戶列表:
const createUser = async () => {
try {
if (!newUserName) return; // 如果沒有輸入用戶名,則不執行操作
await axios.post('/users', { name: newUserName });
setNewUserName(''); // 清空輸入框
fetchUsers(); // 重新獲取用戶列表
} catch (error) {
console.error('Error creating user:', error);
}
};
這些邏輯與新增用戶相似,都是通過發送 HTTP 請求來實現的,具體分別使用 DELETE
和 PUT
請求來刪除或更新用戶。
const deleteUser = async (id) => {
try {
await axios.delete(`/users/${id}`);
fetchUsers(); // 刪除後重新獲取用戶列表
} catch (error) {
console.error('Error deleting user:', error);
}
};
const updateUser = async () => {
try {
if (!userIdToUpdate || !updateUserName) return; // 如果未輸入用戶 ID 或新名稱,則不執行操作
await axios.put(`/users/${userIdToUpdate}`, { name: updateUserName });
setUserIdToUpdate('');
setUpdateUserName('');
fetchUsers(); // 更新後重新獲取用戶列表
} catch (error) {
console.error('Error updating user:', error);
}
};
這些程式碼展示了如何通過 React 的元件化設計來進行頁面開發,以及如何使用 Hooks 來管理狀態和生命週期事件。React Hooks 不僅讓功能性元件變得更直觀,也使得代碼更加簡潔明瞭,讓我們能夠清晰地處理不同的頁面邏輯。
此外,我們也展示了如何通過 react-router-dom
來實現多頁面導航,使得應用能夠在單頁應用 (SPA) 的架構中實現類似傳統多頁應用的頁面切換效果。
這篇我們展示了如何延伸上一個專案範例,展示了 React 如何進行多分頁管理,我們加入了簡單的登入頁來模擬登入後切換分頁的功能,可以看得出來我們通過使用 <Route>
可以輕鬆實現各分頁的路由管理,這樣一來每個分頁就可以用獨立的 React 元件來進行開發,這顯示了在前端開發上的彈性,當然,如果想要限制更嚴格的登入權限,就還需要加入一些登入狀態的判斷機制了。
今天的主題內容都是 React 的前端開發技術面,是不是代表 Rust 的部分都已經講完可以下課了呢?當然沒有,所以下一篇,我們要再拉回來到 Rust 了,不過我們的系列文章已經到了尾聲,下一篇是時候該紮穩馬步了,來個總複習與能力測驗了!